﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace NetFrameworkExtensions.Mathematics
{
  public class MovingHighLowAccumulator
  {
    bool computed;
    double high, low;
    double highValue, lowValue;
    int length;
    int stepSum;
    Queue<Data> queue;

    public double High { get { Compute(); return high; } }
    public double HighValue { get { Compute(); return highValue; } }
    public double Low { get { Compute(); return low; } }
    public double LowValue { get { Compute(); return lowValue; } }

    public MovingHighLowAccumulator(int length)
    {
      Contract.Requires(length > 0);
      this.length = length;
      high = 0;
      low = 0;
      stepSum = 0;
      queue = new Queue<Data>();
      computed = false;
    }

    public void Add(double hlValue, double value, int steps = 1)
    {
      Contract.Requires(steps >= 1);
      var newData = new Data { HLValue = hlValue, Value = value, Steps = steps };
      int toRemove = stepSum + steps - length;
      while (toRemove > 0)
      {
        if (queue.Count == 0)
          break;
        var frontData = queue.Peek();
        if (frontData.Steps > toRemove)
        {
          frontData.Steps -= toRemove;
          stepSum -= toRemove;
          toRemove = 0;
          break;
        }
        else
        {
          queue.Dequeue();
          stepSum -= frontData.Steps;
          toRemove -= frontData.Steps;
        }
      }
      if (toRemove > 0)
        steps = length;
      queue.Enqueue(new Data { HLValue = hlValue, Value = value, Steps = steps });
      stepSum += steps;
      computed = false;
    }

    public void Add(double value, int steps = 1)
    {
      Add(value, value, steps);
    }

    private void Compute()
    {
      if (computed || queue.Count == 0) return;
      bool first = true;
      foreach (var data in queue)
      {
        if (first || data.HLValue > high)
        {
          high = data.HLValue;
          highValue = data.Value;
        }
        if (first || data.HLValue < low)
        {
          low = data.HLValue;
          lowValue = data.Value;
        }
        first = false;
      }
    }

    class Data
    {
      public double HLValue;
      public double Value;
      public int Steps;
    }
  }
}
